Chapter 4: Functions, Numpy and Biopython

Chapter 4: Functions, Numpy and Biopython

4.1 Functions

Imagine this…

You want to calculate the GC content of a sequence.

seq = "ATGCGTAGGCTA"

gc = 0
for n in seq:
    if n == "G" or n == "C":
        gc += 1

print(gc / len(seq))

Now imagine you have 100 sequences…

👉 Would you copy this code 100 times?

❌ Not scalable
❌ Hard to maintain
❌ Error-prone

The solution are Functions. A function is a way to group code that performs a specific task.

👉 Instead of repeating code, we reuse it

🧠 Think of a function as a machine

Input → [ function ] → Output

Using our example

“ATGCGT” → [ GC function ] → 0.5

4.1.1 What is a function?

Python Functions are a block of statements that does a specific task. The idea is to put some commonly or repeatedly done task together and make a function so that instead of writing the same code again and again for different inputs, we can do the function calls to reuse code contained in it over and over again.

image.png

Question: Have you already used functions? Which ones?

Question: What would happen if we didn’t have functions?

Answer here

4.1.2 Function definition

We can define a function, using def keyword. A function might take input in the form of parameters. The syntax to declare a function is:

image.png

—- Let’s do an example! —-

Here, we define a function using def that prints a welcome to UBDS message when called.

def fun():
    print("Welcome to UBDS")

What is happening here?

  • def → tells Python we are defining a function
  • function_name → name of the function
  • parameters → inputs (optional)
  • : → starts the function block
  • Indentation → defines the code inside the function

But it is not printing Welcome to UBDS, why? Because a function only runs when we call it**

4.1.3 Function calling

After creating a function, call it by using the name of the functions followed by parenthesis containing parameters of that particular function.

—- Let’s do an example! —-

Here, we call the function we’ve done before.

fun()

What happens if we define the function but never call it?

Can we call the function multiple times?

Answer here

4.1.4 Function arguments

Arguments are the values passed inside the parenthesis of the function. A function can have any number of arguments separated by a comma. They allow us to give input to the function.

—- Let’s do an example! —-

Now we define a function that takes name as argument:

def welcome(name):
    print("Welcome", name)

welcome("Anna")

What is happening here?

  • name is a parameter (inside the function definition)
  • “Anna”, “John” are arguments (values we pass)

👉 The function behaves differently depending on the input

Very important!!! - Parameter → variable inside the function
- Argument → actual value passed to the function

Python supports various types of arguments that can be passed at the time of the function call. Below are types of function argument types:

4.1.4.1 Default Arguments

A default argument is a parameter that has a default value. If we do not provide a value when calling the function, the default value is used.

Default arguments are optional inputs

👉 If you don’t provide them → Python fills them automatically

—- Let’s do an example! —-

We are printing the values of the x and y axis

def myFun(x, y=50):
    print("x: ", x)
    print("y: ", y)

myFun(10)

What is happening here?

  • We pass only one argument → x = 10
  • y is not provided → Python uses the default value (50)

Question: What will happen if we actually give a value to y too?

# Write your answer here

4.1.4.2 Keyword Arguments

Values are passed by explicitly specifying the parameter names, so the order doesn’t matter.

👉 This means the order does not matter

—- Let’s do an example! —-

We are printing the name of a student and the subject he is enrolled to:

def school(student, subject):
    print(student, subject)

school(student='Anna', subject='Bioinformatics')
school(subject='Bioinformatics', student='Anna')

What is happening here?

  • We specify the parameter names
  • Python knows exactly where each value goes
  • Order does not matter

4.1.4.3 Positional Arguments

Values are assigned to parameters based on their order in the function call.

—- Let’s do an example! —-

We are printing the name of a student and his age:

def nameAge(name, age):
    print("Hi, I am", name)
    print("My age is ", age)

print("Case-1:")
nameAge("Olivia", 27)

print("\nCase-2:")
nameAge(27, "Olivia")

What is happening here?

Case 1:

name = “Olivia”
age = 27

✔️ Correct output


Case 2:

name = 27
age = “Olivia”

❌ Wrong assignment!


–> Python assigns values by position, not by meaning

Positional arguments = values are matched based on order

  • First value → first parameter
  • Second value → second parameter

Question: Can we mix positional and keyword arguments?

# Try the code before mixing both argument types

4.1.4.4 Arbitrary Arguments

Allow a function to accept a variable number of inputs. This is done using two special symbols: * args: collects extra positional (non-keyword) arguments as a tuple. *kwargs: collects extra keyword arguments as a dictionary.

—- Let’s do an example! —-

This code separately shows non-keyword (*args) and keyword (**kwargs) arguments in the same function.

def myFun(*args, **kwargs):
    print("Non-Keyword Arguments (*args):")
    for arg in args:
        print(arg)

    print("\nKeyword Arguments (**kwargs):")
    for key, value in kwargs.items():
        print(f"{key} == {value}")

myFun('Hey', 'Welcome', first='Geeks', mid='for', last='Geeks')

What is happening here?

  • “Hey”, “Welcome” → go into args
  • first=“Geeks”, … → go into kwargs

👉 Python separates positional and keyword arguments automatically

Questions:

- What happens if we pass only positional arguments?  

- What happens if we pass only keyword arguments? 

# Write your answer here

4.1.5 Return Statement

The return statement ends a function and sends a value back to the caller. It can return any data type, multiple values (packed into a tuple), or None if no value is given. It allows the function to produce a result

Syntax:

return [expression]

Parameters: return ends the function, [expression] is the optional value to return (defaults to None).

—- Let’s do an example! —-

This function returns the square value of the entered number

def sq_value(num):
    """This function returns the square
    value of the entered number"""
    return num**2

print(sq_value(2))
print(sq_value(-4))

What is happening here?

  • The function computes num**2
  • return sends the result back
  • print() displays it
def sq_value(num):
    print(num**2)

result = sq_value(2)
print(result)

What happens here?

  • The function prints the value
  • But returns nothing

👉 result = None ❗

  • print() → shows something on screen
  • return → gives a value back

4.2 Python Numpy

Numpy is a general-purpose array-processing package. It provides a high-performance multidimensional array object and tools for working with these arrays. It is the fundamental package for scientific computing with Python. Besides its obvious scientific uses, Numpy can also be used as an efficient multi-dimensional container of generic data.

image.png

4.2.1 Arrays in Numpy

A NumPy array is a structured collection of elements of the same data type stored in a table format. * The number of dimensions is called the rank and the size along each dimension is called the shape. * In NumPy, arrays are called ndarray and elements are accessed using square brackets [], often created from nested Python lists.

4.2.2 Creating a Numpy array

Arrays in Numpy can be created by multiple ways, with various number of Ranks, defining the size of the Array. Arrays can also be created with the use of various data types such as lists, tuples, etc. The type of the resultant array is deduced from the type of the elements in the sequences.

import numpy as np
 
arr = np.array([1, 2, 3])
print(arr)
 
arr = np.array([[1, 2, 3],
                [4, 5, 6]])
print(arr)
 
arr = np.array((1, 3, 2))
print(arr)

What is happening here?

Array with Rank 1: [1 2 3] Array with Rank 2: [[1 2 3] [4 5 6]]

Array created using passed tuple: [1 3 2]

4.2.3 Accessing the Array Index

In a numpy array, indexing or accessing the array index can be done in multiple ways. To print a range of an array, slicing is done. Slicing of an array is defining a range in a new array which is used to print a range of elements from the original array. Since, sliced array holds a range of elements of the original array, modifying content with the help of sliced array modifies the original array content.

import numpy as np
 
arr = np.array([[-1, 2, 0, 4],
                [4, -0.5, 6, 0],
                [2.6, 0, 7, 8],
                [3, -7, 4, 2.0]])

arr2 = arr[:2, ::2]
print ("first 2 rows and alternate columns(0 and 2):\n", arr2)
 
arr3 = arr[[1, 1, 0, 3], 
                [3, 2, 1, 0]]
print ("\nElements at indices (1, 3), "
    "(1, 2), (0, 1), (3, 0):\n", arr3)

4.2.4 Basic Array Operations

In numpy, arrays allow a wide range of operations which can be performed on a particular array or a combination of Arrays. These operations include some basic Mathematical operation as well as Unary and Binary operations.

import numpy as np
 
a = np.array([[1, 2],
              [3, 4]])
 
b = np.array([[4, 3],
              [2, 1]])
               
print ("Adding 1 to every element:", a + 1)
print ("\nSubtracting 2 from each element:", b - 2)
print ("\nSum of all array elements: ", a.sum())
print ("\nArray sum:\n", a + b)

4.3 Biopython

So far, we worked with sequences like:

“ATGCGTAA”

👉 But real data looks like this:

“>seq1”

“ATGCGTAGGCTA”

“>seq2”

“TTAGGCGG”

This is a FASTA file


Problem

How do we read this with Python?

❌ Complex
❌ Error-prone
❌ Not scalable

💡 Solution: Biopython

Biopython is a library designed for working with biological data.

👉 It provides tools to: - Read sequence files (FASTA, GenBank, etc.)
- Manipulate sequences
- Perform biological analyses

https://www.mintlify.com/biopython/biopython

—- Let’s do an example! —-

from Bio import SeqIO

for record in SeqIO.parse("example.fasta", "fasta"):
    print(record.id)
    print(record.seq)

What is happening here?

  • Biopython reads the file
  • Each sequence becomes an object
  • We can access:
    • record.id
    • record.seq

—- Let’s do an example! —-

Operations with sequences

from Bio.Seq import Seq

seq = Seq("ATGCGT")
print(seq.reverse_complement())

4.4 Exercises

Exercise 1

Consider the following function:

def myFun(x):
    print(x * 2)

result = myFun(5)

Questions:

  1. What is printed on the screen?
  2. What is stored in the variable result?
  3. Why? Explain in your own words
# Write your answer here

Exercise 2

Consider the function:

def example(a, b=10, *args, **kwargs):
    pass

And the following function call:

example(5, 20, 30, 40, x=1, y=2)

Questions:

  1. What is the value of a?
  2. What is the value of b?
  3. What is stored in args?
  4. What is stored in kwargs?
  5. Which type of argument is each one?
# Write your answer here

Exercise 3 — Array manipulations

Create a NumPy array with the numbers from 1 to 10.

  1. Multiply all numbers by 3
  2. Add 5 to all numbers
  3. Create a boolean array that is True for numbers greater than 20
  4. Print the result of filtering the original array using this boolean array
# Write your answer here

Exercise 4 — 2D Array manipulations

Create the following 2x3 NumPy array:

[[1, 2, 3], [4, 5, 6]]

  1. Print the shape of the array
  2. Extract the first row
  3. Extract the second column
  4. Multiply the entire array by 2 and print the result
# Write your answer here

Exercise 5 — Reading sequences

Using Biopython, read the sequences from a file example.fasta.

  1. Print the ID of each sequence
  2. Print the length of each sequence
  3. Print the first 10 nucleotides of each sequence
# Write your answer here

Exercise 6 — Sequence operations

Using Biopython:

  1. Create a sequence ATGCGTAC using Seq
  2. Print its complement
  3. Print its reverse complement
  4. Print its length
# Write your answer here